//
// Created by fangp on 3/6/18.
//

#include "runner_config.h"
#include "../../src/init/oop.h"
#include "../base/runner_base.h"

#define OOP_GEN_TEXT "== DEFCLASS ==\n\
\n\
(define (CREATE-%className%) (create-instance MAKE-%className% %properties%))\n\
\n\
(define (MAKE-%className% self %properties%)\n\
(let ((P-%parentClassName% (MAKE-%parentClassName% self %parentProperties%)))\n\
(lambda (message)\n\
(case message\n\
%%property%%\n\
((G-%property%) (lambda () %property%))\n\
((S-%property%) (lambda (NEW-%property%) (set! %property% NEW-%property%)))\n\
%%end%%\n\
%%method%%\n\
((M-%methodName%) (lambda (%methodArgs%) %methodDef%))\n\
%%end%%\n\
((class) (lambda () '%className%))\n\
((parent-class) (lambda () '%parentClassName%))\n\
((is-a) (lambda (type) (case type (('Object) #f) (('%className%) #t) (else (get-method is-a '%parentClassName%)))))\n\
((properties) (lambda () (vector %properties%)))\n\
((methods) (lambda () (vector %methods%)))\n\
(else (get-method message P-%parentClassName%))))))\n\
\n\
== GETPROP ==\n\
\n\
(if (is-null? %object%) () (ask %object% G-%property%))\n\
\n\
== SETPROP ==\n\
\n\
(if (is-null? %object%) () (ask %object% S-%property% %value%))\n\
\n\
== SENDMSG ==\n\
\n\
(ask %object% M-%message% %args%)\n\
\n\
== NEWINSTANCE ==\n\
\n\
(CREATE-%className% %propertyValues%)"

static String oop_code_node_to_string(OopCodeNode *node) {
    String string = init_string();

    if (node->loopBlockName == NULL) {
        if (node->plainBlock->type == TYPE_TEXT) {
            string = concatenate(string, node->plainBlock->code);
        } else if (node->plainBlock->type == TYPE_PARAM) {
            string = concatenate(string, "%");
            string = concatenate(string, node->plainBlock->code);
            string = concatenate(string, "%");
        }
    } else {
        //printf("loop:%s\n", node->loopBlockName);
        string = concatenate(string, "%%");
        string = concatenate(string, node->loopBlockName);
        string = concatenate(string, "%%\n");

        Queue *queue = node->loopBlock;
        QueueNode *queueNode = queue->head;
        while (queueNode != NULL) {
            OopCodeNode *oopCodeNode = (OopCodeNode *)queueNode->element;
            //print_oop_code_node(oopCodeNode);
            string = concatenate(string, oop_code_node_to_string(oopCodeNode));
            queueNode = queueNode->next;
        }
    }
    return string;
}

static String category_code_to_string(String category, Queue *queue) {
    String string = init_string();
    string = concatenate(string, "== ");
    string = concatenate(string, category);
    string = concatenate(string, " ==\n");

    QueueNode *queueNode = queue->head;
    while (queueNode != NULL) {
        OopCodeNode *oopCodeNode = (OopCodeNode *)queueNode->element;
        //print_oop_code_node(oopCodeNode);
        //printf("%s\n", get_oop_code_node(oopCodeNode));
        string = concatenate(string, oop_code_node_to_string(oopCodeNode));
        queueNode = queueNode->next;
    }
    return string;
}

static void test_parse_oop_gen() {
    FILE* fp = create_temp_file(OOP_GEN_TEXT);

    Map map = parse_oop_gen(fp);
    Queue *queue;
    String string = init_string();
    hashmap_get(map, "DEFCLASS", (void**)(&queue));
    string = concatenate(string, category_code_to_string("DEFCLASS", queue));
    hashmap_get(map, "GETPROP", (void**)(&queue));
    string = concatenate(string, category_code_to_string("GETPROP", queue));
    hashmap_get(map, "SETPROP", (void**)(&queue));
    string = concatenate(string, category_code_to_string("SETPROP", queue));
    hashmap_get(map, "SENDMSG", (void**)(&queue));
    string = concatenate(string, category_code_to_string("SENDMSG", queue));
    hashmap_get(map, "NEWINSTANCE", (void**)(&queue));
    string = concatenate(string, category_code_to_string("NEWINSTANCE", queue));

    //printf("######%s######\n", string);
    //printf("######%s######\n", OOP_GEN_TEXT);

    assert_string_equal(string, OOP_GEN_TEXT);
    fclose(fp);
}

static void test_gen_oop_code() {
    FILE* fp = create_temp_file(OOP_GEN_TEXT);
    globalFunny->oopCodes = parse_oop_gen(fp);

    Map params = hashmap_new();
    hashmap_put(params, "className", "SubClass");
    hashmap_put(params, "properties", "prop1 prop2 parentProp1 parentProp2");
    hashmap_put(params, "parentClassName", "SuperClass");
    hashmap_put(params, "parentProperties", "parentProp1 parentProp2");
    hashmap_put(params, "methods", "method1 method2");

    Queue *properties = init_queue();
    Map map = hashmap_new();
    hashmap_put(map, "property", "prop1");
    enqueue(properties, map);

    Queue *methods = init_queue();
    Map map1 = hashmap_new();
    hashmap_put(map1, "methodName", "method1");
    hashmap_put(map1, "methodArgs", "a b");
    hashmap_put(map1, "methodDef", "(+ a b)");
    Map map2 = hashmap_new();
    hashmap_put(map2, "methodName", "method2");
    hashmap_put(map2, "methodArgs", "c d");
    hashmap_put(map2, "methodDef", "(- c d)");
    enqueue(methods, map1);
    enqueue(methods, map2);

    String code = gen_oop_code("DEFCLASS", params, properties, methods);
    printf("%s\n", code);
}


int main(int argc, char **argv) {
    const struct CMUnitTest tests[] = {
            cmocka_unit_test(test_parse_oop_gen),
            cmocka_unit_test(test_gen_oop_code),
    };
    return cmocka_run_group_tests(tests, setup_config, teardown_config);
}

